﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UL360Data;
using System.Net.Mail;
using System.Xml.Linq;
using UL360SurveyService.Models;
using UL360SurveyCommon;

namespace UL360SurveyService.Utils
{
    public static class MailSendingUtil
    {
        #region check to send survey mail
        /// <summary>
        /// Send surveymail for all courses that has ended and where interval for sending survey mail has occured
        /// </summary>
        /// <param name="serviceSettings"></param>
        public static void CheckToSendSurveyMail(Settings serviceSettings)
        {
            //Participants of courses older than this date should have a surveymail sent to them
            DateTime surveyMailThreshold = DateTime.Now.AddDays(-(serviceSettings.SendSurveyMailInterval * serviceSettings.MailIntervalMultiplier));

            //Queryable with all courses and mails sent for them
            List<Kurstillfalle> courses;
            List<SurveyMailedStatus> mailStatus = new List<SurveyMailedStatus>();
            using (var db = new UL360Entities())
            {
                /* Select all courses that has ended after the date in "dontLookForCoursesOlderThan" and were the end date
                 * of the course is "sendSurveyMailInterval" number of weeks age and to wich no mail has
                 * been sent. Order by end date */
                courses = db.Kurstillfalles
                    .Include("SurveyMailedStatus")
                    .Include("Deltagares")
                    .Where(k =>
                        k.slutdatum >= serviceSettings.DontLookForCoursesOlderThan &&
                        k.slutdatum < surveyMailThreshold && 
                        k.SurveyMailedStatus.Count == 0 &&
                        k.aktiv
                        )
                    .OrderBy(k => k.slutdatum)
                    .ToList();

                foreach (Kurstillfalle course in courses)
                {
                    List<SurveyMailedStatus> courseMails = db.SurveyMailedStatus.Where(m => m.fk_KursId == course.kursid).ToList();
                    if (courseMails.Count > 0)
                    {
                        foreach (SurveyMailedStatus mail in courseMails)
                        {
                            mailStatus.Add(mail);
                        }
                    }
                }
            }

            //If courses were found for wich to send mail
            if (courses != null && courses.Count() > 0)
            {
                MailSendingUtil.SendMail(courses, MailType.SurveyMail, serviceSettings, SurveyType.OriginalCourse, mailStatus);
            }
        }
        #endregion

        #region check to send survey reminder mail
        public static void CheckToSendSurveyReminderMail(Settings serviceSettings)
        {
            //Queryable with all courses and mails sent for them
            List<Kurstillfalle> courses;
            List<SurveyMailedStatus> mailStatus = new List<SurveyMailedStatus>();
            using (var db = new UL360Entities())
            {
                /* Select all courses that has ended after the date in "dontLookForCoursesOlderThan" and were the end date
                 * of the course is "sendSurveyMailInterval" number of weeks age and to wich no mail has
                 * been sent. Order by end date */
                var allCourses = db.Kurstillfalles
                    .Include("SurveyMailedStatus")
                    .Include("Deltagares")
                    .Where(k =>
                        k.slutdatum >= serviceSettings.DontLookForCoursesOlderThan &&
                        k.SurveyMailedStatus.Where(m => m.MailType == MailType.ReminderMail).Count() == 0 &&
                        k.SurveyMailedStatus.Where(m => m.MailType == MailType.SurveyMail).Count() > 0 &&
                        k.slutdatum < DateTime.Now &&
                        k.aktiv
                        )
                    .OrderBy(k => k.slutdatum)
                    .ToList();

                courses = allCourses.Where(k =>
                    DateTime.Now > k.SurveyMailedStatus.Where(m => m.MailType == MailType.SurveyMail).OrderByDescending(m => m.DateSent).First().DateSent.AddDays((serviceSettings.SendReminderMailInterval * serviceSettings.MailIntervalMultiplier)))
                    .ToList();

                foreach (Kurstillfalle course in courses)
                {
                    List<SurveyMailedStatus> courseMails = db.SurveyMailedStatus.Where(m => m.fk_KursId == course.kursid).ToList();
                    if (courseMails.Count > 0)
                    {
                        foreach (SurveyMailedStatus mail in courseMails)
                        {
                            mailStatus.Add(mail);
                        }
                    }
                }
            }

            //If courses were found for wich to send mail
            if (courses != null && courses.Count() > 0)
            {
                MailSendingUtil.SendMail(courses, MailType.ReminderMail, serviceSettings, SurveyType.OriginalCourse, mailStatus);
            }
        }
        #endregion

        #region check to send extended course survey mails
        /// <summary>
        /// Send surveymail for all extended courses that has ended and where interval for sending survey mail has occured
        /// </summary>
        /// <param name="serviceSettings"></param>
        public static void CheckToSendExtSurveyMail(Settings serviceSettings)
        {
            //Participants of courses older than this date should have a surveymail sent to them
            DateTime surveyMailThreshold = DateTime.Now.AddDays(-(serviceSettings.SendSurveyMailInterval * serviceSettings.MailIntervalMultiplier));

            //Queryable with all courses and mails sent for them
            List<Kurstillfalle> courses;
            List<SurveyMailedStatus> mailStatus = new List<SurveyMailedStatus>();
            using (var db = new UL360Entities())
            {
                /* Select all courses that has ended after the date in "dontLookForCoursesOlderThan" and were the date 
                 * (fördjupningsdatum) of the course is "sendSurveyMailInterval" number of weeks age and to wich no 
                 * mail has been sent. Order by date */
                courses = db.Kurstillfalles
                    .Include("SurveyMailedStatus")
                    .Include("Deltagares")
                    .Where(k =>
                        k.slutdatum >= serviceSettings.DontLookForCoursesOlderThan && //Leta efter kurser som inte är äldre än det angivna datumet
                        k.DatumFordjupningsdag != null && //Där det finns ett fördjupningsdatum i db
                        k.DatumFordjupningsdag < surveyMailThreshold && //mindre än dagens datum + intervalltid för första mejlet
                        k.DatumFordjupningsdag > k.slutdatum && //Fördjupningsdatum är större än slutdatum (ett krav såklart)
                        k.SurveyMailedStatus.Where(m => m.MailType == MailType.ReminderMail).Count() > 0 && //Där mejlstatus på extsurveymail (2) inte finns i db
                        k.SurveyMailedStatus.Where(m => m.MailType == MailType.ExtSurveyMail).Count() == 0 && //Där mejlstatus på extsurveymail (2) inte finns i db
                        k.SurveyMailedStatus.Where(m => m.MailType == MailType.ExtReminderMail).Count() == 0 && //Där mejlstatus på extremindermail (3) inte finns i db
                        k.aktiv
                        )
                    .OrderBy(k => k.DatumFordjupningsdag)
                    .ToList();

                foreach (Kurstillfalle course in courses)
                {
                    List<SurveyMailedStatus> courseMails = db.SurveyMailedStatus.Where(m => m.fk_KursId == course.kursid).ToList();
                    if (courseMails.Count > 0)
                    {
                        foreach (SurveyMailedStatus mail in courseMails)
                        {
                            mailStatus.Add(mail);
                        }
                    }
                }
            }

            //If courses were found for wich to send mail
            if (courses != null && courses.Count() > 0)
            {
                MailSendingUtil.SendMail(courses, MailType.ExtSurveyMail, serviceSettings, SurveyType.ExtendedCourse, mailStatus);
            }
        }
        #endregion

        #region check to send extended course reminder mail
        public static void CheckToSendExtSurveyReminderMail(Settings serviceSettings)
        {
            //Queryable with all extended courses and mails sent for them
            List<Kurstillfalle> courses;
            List<SurveyMailedStatus> mailStatus = new List<SurveyMailedStatus>();
            using (var db = new UL360Entities())
            {
                /* Select all extended courses that has ended after the date in "dontLookForCoursesOlderThan" and were the date
                 * (fördjupningsdatum) of the course is "sendSurveyMailInterval" number of weeks age and to wich no mail has
                 * been sent. Order by end date */
                var allCourses = db.Kurstillfalles
                    .Include("SurveyMailedStatus")
                    .Include("Deltagares")
                    .Where(k =>
                        k.slutdatum >= serviceSettings.DontLookForCoursesOlderThan && //leta inte efter kurser äldre än angivet datum
                        k.SurveyMailedStatus.Where(m => m.MailType == MailType.ExtReminderMail).Count() == 0 && //Kurser som inte skickat extended påminnelsemejl
                        k.SurveyMailedStatus.Where(m => m.MailType == MailType.ExtSurveyMail).Count() > 0 && //Men som har skickat original extended mejl
                        k.DatumFordjupningsdag < DateTime.Now && //Där fördjupningsdatum är mindre än dagens datum
                        k.aktiv
                        )
                    .OrderBy(k => k.DatumFordjupningsdag)
                    .ToList();

                courses = allCourses.Where(k =>
                    DateTime.Now > k.SurveyMailedStatus.Where(m => m.MailType == MailType.ExtSurveyMail).OrderByDescending(m => m.DateSent).First().DateSent.AddDays((serviceSettings.SendReminderMailInterval * serviceSettings.MailIntervalMultiplier)))
                    .ToList();

                foreach (Kurstillfalle course in courses)
                {
                    List<SurveyMailedStatus> courseMails = db.SurveyMailedStatus.Where(m => m.fk_KursId == course.kursid).ToList();
                    if (courseMails.Count > 0)
                    {
                        foreach (SurveyMailedStatus mail in courseMails)
                        {
                            mailStatus.Add(mail);
                        }
                    }
                }
            }

            //If courses were found for wich to send mail
            if (courses != null && courses.Count() > 0)
            {
                MailSendingUtil.SendMail(courses, MailType.ExtReminderMail, serviceSettings, SurveyType.ExtendedCourse, mailStatus);
            }
        }
        #endregion

        /// <summary>
        /// Send mail for courses, depending on mailtype
        /// </summary>
        /// <param name="courses"></param>
        /// <param name="mailType"></param>
        /// <param name="serviceSettings"></param>
        public static void SendMail(List<Kurstillfalle> courses, MailType mailType, Settings serviceSettings, 
            SurveyType surveyType, List<SurveyMailedStatus> surveyMails)
        {
            List<UL360SurveyCommon.Models.GenericMailTemplate> templates = null;

            //Get mail template depending on mailtype
            if (mailType.Equals(MailType.ReminderMail))
            {
                templates = Templating.LoadTemplate(serviceSettings.ExePath + serviceSettings.SurveyReminderMailTemplatePath);
            }
            else if (mailType.Equals(MailType.SurveyMail))
            {
               templates = Templating.LoadTemplate(serviceSettings.ExePath + serviceSettings.SurveyMailTemplatePath);
            }
            else if (mailType.Equals(MailType.ExtSurveyMail))
            {
                templates = Templating.LoadTemplate(serviceSettings.ExePath + serviceSettings.ExtSurveyMailTemplatePath);
            }
            else
            {
                templates = Templating.LoadTemplate(serviceSettings.ExePath + serviceSettings.ExtSurveyReminderMailTemplatePath);
            }
            
            //Mailtemplates for error reports
            List<ErrorReportMailTemplate> errorReportMailTemplates = null;
            errorReportMailTemplates = MailSendingUtil.LoadErrorReportMailTemplate(serviceSettings.ExePath + serviceSettings.ErrorReportMailTemplatePath);

            //Smtpclient
            SmtpClient smtp = InitializeSMTPClient(serviceSettings);

            //List of courses with successfull mails
            List<SurveyMailedStatus> mailStatus = new List<SurveyMailedStatus>();

            //Get a string containing all courses to wich mail is going to be sent
            string coursesString = "";
            courses.ForEach(delegate(Kurstillfalle c){ coursesString += "Name: " + c.organisation + ", ID: " + c.kursid + ", Date: " + 
                c.startdatum.ToShortDateString() + " -- " + c.slutdatum.ToShortDateString() + "\n"; });

            //Log that service is going to send mails
            UL360SurveyCommon.LoggerCommon.LogNewEntry(new UL360SurveyCommon.LoggerCommon.LogNewEntryDelegate(Logger.LogNewEntry),
                System.Reflection.MethodBase.GetCurrentMethod().ToString(),
                "Starting to send mail for following courses: \n " + coursesString,
                (int)UL360SurveyCommon.SurveyLoggingLevel.Information, true, serviceSettings.EventlogSource);

            //Go through all courses to mail
            foreach (Kurstillfalle course in courses)
            {
                //List of errormessages that occured while mailing participants of current course in loop
                List<SmtpSendError> errorMessages = new List<SmtpSendError>();

                //Send mail to all participants
                foreach (Deltagare d in course.Deltagares.ToList())
                {
                    //Create new mailsmessage from template
                    MailMessage mail = new MailMessage(serviceSettings.FromAddress, d.email);
                    UL360SurveyCommon.Models.GenericMailTemplate template = templates.Where(t => t.languageId == d.sprakid).First();

                    //Replace values in subject with values for current participant in loop
                    mail.Subject = MailTemplate.InsertUserValues(template.Subject, d);
                    
                    //Replace values in template with values for current participant in loop
                    string message = MailTemplate.InsertCourseValues(template.Message, course);

                    //Replace values in template with values for current course in loop
                    message = MailTemplate.InsertUserValues(message, d);

                    //Set wether mailbody is HTML
                    mail.IsBodyHtml = template.IsHtml;
                    string linkFormat;

                    //If mailbody is HTML, replace values with html values
                    if (mail.IsBodyHtml)
                    {
                        message = message.Replace("\n", "<br />").Replace("\r\n", "<br />");
                        linkFormat = "<a href=\"{0}\">{0}</a>";
                    }
                    //If not HTML
                    else
                    {
                        linkFormat = "{0}";
                    }

                    //Replace the url to the form with correct values
                    string url = serviceSettings.SurveyUrl + "?formType=" + (int)surveyType + "&sa=" + URLEncrypt.Encrypt(d.deltagarid.ToString());
                    message = message.Replace("#url#", string.Format(linkFormat, url));

                    //Replace #latestAnswerDate with the lastAnswerdate for the course
                    DateTime lastAnswerDate = DateTime.Now.AddDays(
                        (serviceSettings.SendReminderMailInterval * serviceSettings.MailIntervalMultiplier) + 
                        (serviceSettings.LastAnswerDateInterval * serviceSettings.MailIntervalMultiplier));

                    //Set last answerdate from mails sent, i.e. if mail was delayed, set last answer date with regard for this
                    if (surveyMails.Where(m => m.MailType == MailType.SurveyMail).DefaultIfEmpty(null).First() != null | surveyMails.Where(m => m.MailType == MailType.ExtSurveyMail).DefaultIfEmpty(null).First() != null)
                    {
                        //If surveyMail was sent, set lastanswerdate to SendReminderInterval + LastAnswerInterval from SendSurveyMailInterval
                        lastAnswerDate = DateTime.Now.AddDays((serviceSettings.LastAnswerDateInterval * serviceSettings.MailIntervalMultiplier));
                    }

                    message = message.Replace("#lastAnswerDate#", lastAnswerDate.ToShortDateString());

                    //Set mailbody
                    mail.Body = message;

                    //Try sending message, if unsuccessfull, add to errorMessages
                    try
                    {
                        //Send mail
                        smtp.Send(mail);
                    }
                    catch (SmtpException exc)
                    {
                        //Create new error message
                        errorMessages.Add(new SmtpSendError
                        {
                            Person = string.Format("{0} {1}", d.fornamn, d.efternamn),
                            MailAddress = d.email,
                            SmtpStatusCode = exc.StatusCode.ToString(),
                            ExceptionMessage = exc.ToString(),
                            InnerExceptionMessage = exc.InnerException.ToString()
                        });
                    }
                }

                //If any errors did occur
                if (errorMessages.Count > 0)
                {
                    string errorsAsString = SmtpSendError.ToString(errorMessages);

                    /* If the number of errormessages corresponds to the number of participants in the course, no mails has been 
                     * sent wich indicates that something is wrong with the smtp-server */
                    if (errorMessages.Count == course.Deltagares.Count)
                    {
                        LoggerCommon.LogNewEntry(
                            new LoggerCommon.LogNewEntryDelegate(Logger.LogNewEntry), 
                            System.Reflection.MethodBase.GetCurrentMethod().ToString()
                            , "No email-messages sent for course: " + course.kursid + 
                            ", following errors occured for mailmessages:\n" + errorsAsString, 
                            (int)SurveyLoggingLevel.Error, true, serviceSettings.EventlogSource);

                        //Try sending error report to ErrorReportsReceiver
                        try
                        {
                            //Get error template for error report when no mails could be sent for course
                            ErrorReportMailTemplate errorTemplate = errorReportMailTemplates
                                    .Where(t => t.ErrorReportType == ErrorReportType.NoMailsSentForCourse).First();
                            
                            //Create mailmessage
                            MailMessage errorReport = new MailMessage(
                                serviceSettings.FromAddress,
                                serviceSettings.ErrorReportsReceiver
                                );

                            //Set subject and wether mail should be sent as html
                            errorReport.Subject = errorTemplate.Subject;
                            errorReport.IsBodyHtml = errorTemplate.IsHtml;

                            //If mailbody is HTML, replace values with html values
                            if (errorReport.IsBodyHtml)
                            {
                                errorTemplate.Message = errorTemplate.Message.Replace("\n", "<br />").Replace("\r\n", "<br />");
                            }

                            //Replace dynamic values
                            errorTemplate.Message = errorTemplate.Message.Replace("#course#", course.organisation);
                            errorTemplate.Message = errorTemplate.Message.Replace("#participants#", SmtpSendError.AllPersons(errorMessages));

                            //Set errortemplatemessage as mailbody
                            errorReport.Body = errorTemplate.Message;

                            //Send mail
                            smtp.Send(errorReport);
                        }
                        //Catch smtp-exception
                        catch (SmtpException exc)
                        {
                            //Log to log and eventlog
                            LoggerCommon.LogNewEntry(new LoggerCommon.LogNewEntryDelegate(Logger.LogNewEntry), 
                                System.Reflection.MethodBase.GetCurrentMethod().ToString(),
                                string.Format("An error occurred while sending report of unsuccessfully sent mails, SMTP Error code: {0}\n\n Exception: ", exc.StatusCode.ToString(), exc.ToString()),
                                (int)SurveyLoggingLevel.Error, true, serviceSettings.EventlogSource);
                        }
                        //Catch other exceptions
                        catch(Exception exc)
                        {

                            LoggerCommon.LogNewEntry(new LoggerCommon.LogNewEntryDelegate(Logger.LogNewEntry),
                                System.Reflection.MethodBase.GetCurrentMethod().ToString(),
                                string.Format("An error occurred while sending report of unsuccessfully sent mails, exception: {0}", exc.ToString()),
                                (int)SurveyLoggingLevel.Error, true, serviceSettings.EventlogSource);
                        }
                    }
                    /* If there are errors but not as much as participants, this indicates that smtp-server is working but 
                     * one or several participant addresses is incorrect */
                    else if(errorMessages.Count < course.Deltagares.Count)
                    {
                        LoggerCommon.LogNewEntry(
                            new LoggerCommon.LogNewEntryDelegate(Logger.LogNewEntry), 
                            System.Reflection.MethodBase.GetCurrentMethod().ToString()
                            , "One/some emailmessage(s) could not be sent for course: " + course.kursid +
                            ", following error(s) occured for mailmessage(s):\n" + errorsAsString,
                            (int)SurveyLoggingLevel.Error, true, serviceSettings.EventlogSource);
                    }
                }

                //If some mails could be sent, add course to successfull list
                if (errorMessages.Count != course.Deltagares.Count)
                {
                    mailStatus.Add(new SurveyMailedStatus
                    {
                        DateSent = DateTime.Now,
                        fk_KursId = course.kursid,
                        MailType = mailType
                    });
                }
            }

            //Add status for all courses to wich mail could be sent
            using (var db = new UL360Entities())
            {
                //Loop through all mailstatuses and add to database
                foreach (SurveyMailedStatus status in mailStatus)
                {
                    db.SurveyMailedStatus.Add(
                        new SurveyMailedStatus 
                        { 
                            DateSent = status.DateSent, 
                            fk_KursId = status.fk_KursId, 
                            MailType = status.MailType 
                        }
                    );
                }
                    
                db.SaveChanges();
            }
        }

        //Initialize the smtp client
        public static SmtpClient InitializeSMTPClient(Settings serviceSettings)
        {
            //SMTP-client for mailing
            SmtpClient smtp = new SmtpClient(serviceSettings.SMTPHost, serviceSettings.SMTPPort);
            System.Net.NetworkCredential credentials = new System.Net.NetworkCredential(serviceSettings.SMTPUser, serviceSettings.SMTPPassword);
            smtp.Credentials = credentials;

            return smtp;
        }


        /// <summary>
        /// Load an XML-mailtemplate for error reports
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static List<ErrorReportMailTemplate> LoadErrorReportMailTemplate(string path)
        {
            //Create list of templates
            List<ErrorReportMailTemplate> templates = new List<ErrorReportMailTemplate>();

            //Load XML-template file
            XElement root = XElement.Load(path);

            //Loop through each language template
            foreach (XElement element in root.Descendants("template"))
            {
                //Get ErrorReportType
                int errorType; 
                int.TryParse(element.Attribute("errorReportType").Value, out errorType);

                //Get if Message should be in HTML-format
                bool isHtml;
                bool.TryParse(element.Element("message").Attribute("isHtml").Value, out isHtml);

                //Add template to templatelist
                templates.Add(new ErrorReportMailTemplate
                {
                    Subject = element.Element("subject").Value,
                    Message = element.Element("message").Value,
                    IsHtml = isHtml,
                    ErrorReportType = (ErrorReportType)errorType
                });
            }

            //Return all templates
            return templates;
        }
    }
}
